package org.swan.web.configuration.validator;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.swan.web.exception.BusinessCode;
import org.swan.web.exception.BusinessException;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class ValidNameValidator implements ConstraintValidator<MethodValidate, Object> {
    private static final Map<String, Callable> cache = new HashMap<>();

    private String beanName;
    private String methodName;
    private String fullName;
    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void initialize(MethodValidate constraintAnnotation) {
        this.fullName = constraintAnnotation.value();
        Pattern pattern = Pattern.compile("^([\\w\\.\\-_]+)#([_A-Za-z][\\w_\\-]+)$");
        Matcher matcher = pattern.matcher(fullName);
        if (matcher.matches()) {
            beanName = matcher.group(1);
            methodName = matcher.group(2);
        } else {
            throw new BusinessException(BusinessCode.VALIDATION_PATTERN_INVALID);
        }
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (cache.get(fullName) == null) {
            Object bean = applicationContext.getBean(beanName);
            Optional<Method> method = Arrays.stream(bean.getClass().getMethods()).filter(m -> m.getName().equals(this.methodName))
                    .filter(m -> m.getParameterCount() == 1)
                    .filter(m -> m.getParameterTypes()[0].isAssignableFrom(value.getClass()))
                    .filter(m -> m.getReturnType() == Boolean.class || m.getReturnType() == boolean.class).findAny();
            cache.put(fullName, method.isEmpty() ? Callable.UNMATCHED_METHOD : new Callable(bean, method.get()));
        }
        Callable callable = cache.get(fullName);
        if (callable == Callable.UNMATCHED_METHOD)
            throw new BusinessException(BusinessCode.UNMATCHED_METHOD);
        return callable.call(value);
    }

    static class Callable {
        protected static final Callable UNMATCHED_METHOD = new Callable(null, null);

        private final Object instance;
        private final Method method;

        public Callable(Object instance, Method method) {
            this.instance = instance;
            this.method = method;
        }

        public boolean call(Object value) {
            try {
                return (boolean) method.invoke(instance, value);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
